Companion notebook for the Introduction to dplyr slides
Motivation
Often you will need to create some new variables or summaries, or maybe you just want to rename the variables or reorder the observations to make the data a little easier to work with.
We will focus on how to use the dplyr package, another core member of the tidyverse.
Prerequisites: Install the nycflights13 and tidyverse packages
library(tidyverse)
library(nycflights13)
Data from nycflights13
This dataset contains flights departing New York City (NYC) in 2013. It contains all 336,776 flights that departed from NYC in 2013.

The data comes from the US Bureau of Transportation Statistics, and is documented in ?flights
The tidyverse
The tidyverse is an opinionated collection of R packages designed for data science. All packages share an underlying design philosophy, grammar, and data structures.
Definition of tidy data: (see paper
Each variable is a column
Each observation is a row
Each type of observational unit is a table
Core tidyverse packages

dplyr basics

Pick observations by their values: filter()
Reorder the rows: arrange()
Pick variables by their names: select()
Create new variables with functions of existing variables: mutate()
Collapse many values down to a single summary: summarize()
Operate on a group-by-group basis: group_by()
Filtering rows
filter() allows you to subset observations based on their values. For example, we can select all flights on January 1st with:
filter(flights, month == 1, day == 1)
Comparisons
dplyr functions never modify their inputs, so if you want to save the result, you will need to use the assignment operator, <-
jan1 <- filter(flights, month == 1, day == 1)
jan1
To use filtering effectively, you have to know how to select the observations that you want using comparison operators:
>, >=, <, <=,
!= (not equal), and
== (equal).
Logical operators
]
Flights in November OR December
The following code finds all flights that departed in November OR December:
filter(flights, month == 11 | month == 12)
A useful short-hand is x %in% y. This will select every row where x is one of the values in y. We could use it to rewrite the code above:
nov_dec <- filter(flights, month %in% c(11, 12))
nov_dec
Arrange rows with arrange()
arrange() works similarly to filter() except that instead of selecting rows, it changes their order.
It takes a data frame and a set of column names to order by.
arrange(flights, year, month, day)
Use desc() to re-order by a column in descending order:
arrange(flights, desc(arr_delay))
Select columns with select()
select() allows you to rapidly zoom in on a useful subset using the names of the variables.
# Select columns by name
select(flights, year, month, day)
There are a number of helper functions you can use within select():
starts_with("abc"): matches names that begin with "abc".
ends_with("xyz"): matches names that end with "xyz".
contains("ijk"): matches names that contain "ijk".
Add new variables with mutate()
To add new columns that are functions of existing columns. That is the job of mutate(). mutate() always adds new columns at the end of your dataset.
# create a smaller dataset with less columns
flights_sml <- select(flights,
year:day,
ends_with("delay"),
distance,
air_time
)
flights_sml
Example: using mutate()
mutate(flights_sml,
gain = arr_delay - dep_delay,
speed = distance / air_time * 60
)
flights_sml
Note that you can refer to columns that you have just created:
mutate(flights_sml,
gain = arr_delay - dep_delay,
hours = air_time/60,
gain_per_hour = gain/hours
)
Grouped summaries with summarise()
summarise() collapses a data frame to a single row:
summarise(flights, delay = mean(dep_delay, na.rm = TRUE))
The summarise() function is useful when we pair it with group_by().
This way, the analysis can be done for individual groups.
The pipe operator
The pipe operator is given by %>% (from the magrittr package)
The pipe sends the output of the LHS function to the first argument of the RHS function. For example:
# pipe example
sum(1:8) %>%
sqrt() %>%
log()
[1] 1.791759
is equivalent to
# the log of the square root
# of the sum of the elements
# of the vector [1 ... 8]
log(sqrt(sum(1:8)))
[1] 1.791759
Example for data exploration
Imagine that we want to explore the relationship between the distance and average delay for each location.
There are three steps to prepare this data:
Group flights by destination.
Summarize to compute distance, average delay, and number of flights.
Filter to remove noisy points and Honolulu airport, which is almost twice as far away as the next closest airport.
Power of the pipe %>% operator
# create dataframe of "delays"
delays <- flights %>%
group_by(dest) %>%
summarise(
count = n(),
dist = mean(distance, na.rm = TRUE),
delay = mean(arr_delay, na.rm = TRUE)
) %>%
filter(count > 20, dest != "HNL")
delays
If you use RStudio, you can type the pipe with Ctrl + Shift + M if you have a PC or Cmd + Shift + M if you have a Mac.
You can read it as a series of imperative statements: group, then summarize, then filter. A good way to pronounce %>% when reading code is “then”.
The n() function is implemented specifically for each data source and can be used from within summarize(), mutate() and filter(). It returns the number of observations in the current group.
LS0tDQp0aXRsZSA6ICJJbnRyb2R1Y3Rpb24gdG8gYGRwbHlyYCINCm91dHB1dDogaHRtbF9ub3RlYm9vaw0KLS0tDQoNCg0KPiBDb21wYW5pb24gbm90ZWJvb2sgZm9yIHRoZSBbSW50cm9kdWN0aW9uIHRvIGBkcGx5cmAgc2xpZGVzXShodHRwczovL3d3dy5yZWlzYW5hci5jb20vc2xpZGVzL2ludHJvZHVjaW5nLWRwbHlyIzEpIA0KDQojIE1vdGl2YXRpb24NCg0KDQotIE9mdGVuIHlvdSB3aWxsIG5lZWQgdG8gY3JlYXRlIHNvbWUgKm5ldyB2YXJpYWJsZXMqIG9yICpzdW1tYXJpZXMqLCBvciBtYXliZSB5b3UganVzdCB3YW50IHRvICpyZW5hbWUgKnRoZSB2YXJpYWJsZXMgb3IgKnJlb3JkZXIqIHRoZSBvYnNlcnZhdGlvbnMgdG8gbWFrZSB0aGUgZGF0YSBhIGxpdHRsZSBlYXNpZXIgdG8gd29yayB3aXRoLg0KDQotIFdlIHdpbGwgZm9jdXMgb24gaG93IHRvIHVzZSB0aGUgYGRwbHlyYCBwYWNrYWdlLCBhbm90aGVyIGNvcmUgbWVtYmVyIG9mIHRoZSBgdGlkeXZlcnNlYC4gDQoNCioqUHJlcmVxdWlzaXRlczoqKiBJbnN0YWxsIHRoZSBgbnljZmxpZ2h0czEzYCBhbmQgYHRpZHl2ZXJzZWAgcGFja2FnZXMNCg0KDQpgYGB7ciwgbWVzc2FnZT1GQUxTRSxlY2hvPVRSVUV9DQpsaWJyYXJ5KHRpZHl2ZXJzZSkNCmxpYnJhcnkobnljZmxpZ2h0czEzKQ0KYGBgDQoNCg0KIyBEYXRhIGZyb20gYG55Y2ZsaWdodHMxM2AgDQoNCg0KVGhpcyBkYXRhc2V0IGNvbnRhaW5zIGZsaWdodHMgZGVwYXJ0aW5nIE5ldyBZb3JrIENpdHkgKE5ZQykgaW4gMjAxMy4gSXQgY29udGFpbnMgYWxsIDMzNiw3NzYgZmxpZ2h0cyB0aGF0IGRlcGFydGVkIGZyb20gTllDIGluIDIwMTMuIA0KDQpgYGB7ciwgZWNobyA9IEZ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cHM6Ly9yYXcuZ2l0aHVidXNlcmNvbnRlbnQuY29tL3JlaXNhbmFyL2ZpZ3MvbWFzdGVyL2ZsaWdodHMxMy5wbmciKQ0KYGBgDQoNCg0KVGhlIGRhdGEgY29tZXMgZnJvbSB0aGUgW1VTIEJ1cmVhdSBvZiBUcmFuc3BvcnRhdGlvbiBTdGF0aXN0aWNzXShodHRwczovL3d3dy50cmFuc3RhdHMuYnRzLmdvdi9EYXRhYmFzZUluZm8uYXNwP0RCX0lEPTEyMCZMaW5rPTApLCBhbmQgaXMgZG9jdW1lbnRlZCBpbiBgP2ZsaWdodHNgDQoNCg0KDQojIFRoZSBgdGlkeXZlcnNlYA0KDQoNClRoZSBgdGlkeXZlcnNlYCBpcyBhbiBvcGluaW9uYXRlZCBbY29sbGVjdGlvbiBvZiBSIHBhY2thZ2VzXShodHRwczovL3d3dy50aWR5dmVyc2Uub3JnL3BhY2thZ2VzLykgZGVzaWduZWQgZm9yIGRhdGEgc2NpZW5jZS4gQWxsIHBhY2thZ2VzIHNoYXJlIGFuIHVuZGVybHlpbmcgZGVzaWduIHBoaWxvc29waHksIGdyYW1tYXIsIGFuZCBkYXRhIHN0cnVjdHVyZXMuDQoNCg0KRGVmaW5pdGlvbiBvZiAqKnRpZHkgZGF0YSoqOiAoc2VlIFtwYXBlcl0oKGh0dHBzOi8vdml0YS5oYWQuY28ubnovcGFwZXJzL3RpZHktZGF0YS5wZGYpIGJ5IEhhZGxleSBXaWNraGFtKQ0KDQotIEVhY2ggdmFyaWFibGUgaXMgYSBjb2x1bW4NCg0KLSBFYWNoIG9ic2VydmF0aW9uIGlzIGEgcm93DQoNCi0gRWFjaCB0eXBlIG9mIG9ic2VydmF0aW9uYWwgdW5pdCBpcyBhIHRhYmxlDQoNCg0KIyBDb3JlIGB0aWR5dmVyc2VgIHBhY2thZ2VzDQoNCg0KfCBQYWNrYWdlIE5hbWUgICAgICAgICAgICAgICAgICAgICAgICAgICAgICAgIHwgIFVzYWdlICAgICAgICAgICAgICAgICB8DQp8LS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tfC0tLS0tLS0tLS0tLS0tLS0tLS0tLS0tLXwNCnwgW2BnZ3Bsb3QyYF0oaHR0cHM6Ly9nZ3Bsb3QyLnRpZHl2ZXJzZS5vcmcvKSB8IGRhdGEgdmlzdWFsaXphdGlvbiAgICAgfA0KfCBbYGRwbHlyYF0oaHR0cHM6Ly9kcGx5ci50aWR5dmVyc2Uub3JnLykgICAgIHwgZGF0YSBtYW5pcHVsYXRpb24gICAgICB8DQp8IFtgdGlkeXJgXShodHRwczovL3RpZHlyLnRpZHl2ZXJzZS5vcmcvKSAgICAgfCB0aWR5IHVwIGRhdGEgICAgICAgICAgIHwNCnwgW2ByZWFkcmBdKGh0dHBzOi8vcmVhZHIudGlkeXZlcnNlLm9yZy8pICAgICB8IGRhdGEgaW1wb3J0ICAgICAgICAgICAgfA0KfCBbYHB1cnJyYF0oaHR0cHM6Ly9wdXJyci50aWR5dmVyc2Uub3JnLykgICAgIHwgZnVuY3Rpb25hbCBwcm9ncmFtbWluZyB8DQp8IFtgdGliYmxlYF0oaHR0cHM6Ly90aWJibGUudGlkeXZlcnNlLm9yZy8pICAgfCBkYXRhZnJhbWVzIHJlaW1hZ2luZWQgIHwNCnwgW2BzdHJpbmdyYF0oaHR0cHM6Ly9zdHJpbmdyLnRpZHl2ZXJzZS5vcmcvKSB8IHdvcmtpbmcgd2l0aCBzdHJpbmdzICAgfA0KfCBbYGZvcmNhdHNgXShodHRwczovL2ZvcmNhdHMudGlkeXZlcnNlLm9yZy8pfCAgd29ya2luZyB3aXRoIGZhY3RvcnMgICB8DQoNCg0KDQpgYGB7ciwgb3V0LndpZHRoID0gIjEwMCUiLCBmaWcuYWxpZ249J2NlbnRlcicsIGVjaG8gPSBGfQ0Ka25pdHI6OmluY2x1ZGVfZ3JhcGhpY3MoImh0dHBzOi8vcmF3LmdpdGh1YnVzZXJjb250ZW50LmNvbS9yZWlzYW5hci9maWdzL21hc3Rlci90aWR5dmVyc2UtcGFja2FnZXMucG5nIikNCmBgYA0KDQoNCg0KDQojIGBkcGx5cmAgYmFzaWNzDQoNCg0KYGBge3IsIGVjaG8gPSBGLCBmaWcuc2hvdz0naG9sZCcsIG91dC5oZWlnaHQ9IjI1JSIsIGZpZy5hbGlnbj0nY2VudGVyJ30NCmtuaXRyOjppbmNsdWRlX2dyYXBoaWNzKA0KICBjKCJodHRwczovL3Jhdy5naXRodWJ1c2VyY29udGVudC5jb20vcmVpc2FuYXIvZmlncy9tYXN0ZXIvZHBseXItbmV3LW9sZC5wbmciKQ0KKQ0KYGBgDQoNCg0KMS4gKipQaWNrIG9ic2VydmF0aW9ucyoqIGJ5IHRoZWlyIHZhbHVlczogYGZpbHRlcigpYA0KDQoyLiAqKlJlb3JkZXIqKiB0aGUgcm93czogYGFycmFuZ2UoKWANCg0KMy4gKipQaWNrIHZhcmlhYmxlcyoqIGJ5IHRoZWlyIG5hbWVzOiBgc2VsZWN0KClgDQoNCjQuICoqQ3JlYXRlIG5ldyB2YXJpYWJsZXMqKiB3aXRoIGZ1bmN0aW9ucyBvZiBleGlzdGluZyB2YXJpYWJsZXM6IGBtdXRhdGUoKWANCg0KNS4gKipDb2xsYXBzZSoqIG1hbnkgdmFsdWVzIGRvd24gdG8gYSBzaW5nbGUgc3VtbWFyeTogYHN1bW1hcml6ZSgpYA0KDQo2LiBPcGVyYXRlIG9uIGEgKipncm91cC1ieS1ncm91cCoqIGJhc2lzOiBgZ3JvdXBfYnkoKWANCg0KDQoNCg0KIyBGaWx0ZXJpbmcgX3Jvd3NfDQoNCg0KYGZpbHRlcigpYCBhbGxvd3MgeW91IHRvIHN1YnNldCBvYnNlcnZhdGlvbnMgYmFzZWQgb24gdGhlaXIgdmFsdWVzLiAgRm9yIGV4YW1wbGUsIHdlIGNhbiBzZWxlY3QgYWxsIGZsaWdodHMgb24gSmFudWFyeSAxc3Qgd2l0aDoNCg0KDQoNCmBgYHtyfQ0KZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDEsIGRheSA9PSAxKQ0KYGBgDQoNCg0KDQoNCiMgQ29tcGFyaXNvbnMNCg0KYGRwbHlyYCBmdW5jdGlvbnMgbmV2ZXIgbW9kaWZ5IHRoZWlyIGlucHV0cywgc28gaWYgeW91IHdhbnQgdG8gc2F2ZSB0aGUgcmVzdWx0LCB5b3Ugd2lsbCBuZWVkIHRvIHVzZSB0aGUgYXNzaWdubWVudCBvcGVyYXRvciwgYDwtYA0KDQpgYGB7cn0NCmphbjEgPC0gZmlsdGVyKGZsaWdodHMsIG1vbnRoID09IDEsIGRheSA9PSAxKQ0KamFuMQ0KYGBgDQoNCg0KDQpUbyB1c2UgZmlsdGVyaW5nIGVmZmVjdGl2ZWx5LCB5b3UgaGF2ZSB0byBrbm93IGhvdyB0byBzZWxlY3QgdGhlIG9ic2VydmF0aW9ucyB0aGF0IHlvdSB3YW50IHVzaW5nIGNvbXBhcmlzb24gb3BlcmF0b3JzOiANCg0KYD5gLCBgPj1gLCBgPGAsIGA8PWAsIA0KDQpgIT1gIChub3QgZXF1YWwpLCBhbmQgDQoNCmA9PWAgKGVxdWFsKS4NCg0KDQoqKkxvZ2ljYWwgb3BlcmF0b3JzKioNCg0KYGBge3IsIG91dC53aWR0aCA9ICI2MDBweCIsIGZpZy5hbGlnbj0nY2VudGVyJywgZWNobyA9IEZ9DQprbml0cjo6aW5jbHVkZV9ncmFwaGljcygiaHR0cDovL3I0ZHMuaGFkLmNvLm56L2RpYWdyYW1zL3RyYW5zZm9ybS1sb2dpY2FsLnBuZyIpDQpgYGANCl0NCg0KDQojIEZsaWdodHMgaW4gTm92ZW1iZXIgX09SXyBEZWNlbWJlcg0KDQpUaGUgZm9sbG93aW5nIGNvZGUgZmluZHMgYWxsIGZsaWdodHMgdGhhdCBkZXBhcnRlZCBpbiBOb3ZlbWJlciBPUiBEZWNlbWJlcjoNCg0KYGBge3J9DQpmaWx0ZXIoZmxpZ2h0cywgbW9udGggPT0gMTEgfCBtb250aCA9PSAxMikNCmBgYA0KDQpBIHVzZWZ1bCBzaG9ydC1oYW5kIGlzIGB4ICVpbiUgeWAuIFRoaXMgd2lsbCBzZWxlY3QgZXZlcnkgcm93IHdoZXJlIGB4YCBpcyBvbmUgb2YgdGhlIHZhbHVlcyBpbiBgeWAuIFdlIGNvdWxkIHVzZSBpdCB0byByZXdyaXRlIHRoZSBjb2RlIGFib3ZlOg0KDQpgYGB7cn0NCm5vdl9kZWMgPC0gZmlsdGVyKGZsaWdodHMsIG1vbnRoICVpbiUgYygxMSwgMTIpKQ0Kbm92X2RlYw0KYGBgDQoNCg0KDQojIEFycmFuZ2Ugcm93cyB3aXRoIGBhcnJhbmdlKClgDQoNCmBhcnJhbmdlKClgIHdvcmtzIHNpbWlsYXJseSB0byBgZmlsdGVyKClgIGV4Y2VwdCB0aGF0IGluc3RlYWQgb2Ygc2VsZWN0aW5nIHJvd3MsIGl0ICoqY2hhbmdlcyB0aGVpciBvcmRlcioqLiANCg0KSXQgdGFrZXMgYSBkYXRhIGZyYW1lIGFuZCBhIHNldCBvZiBjb2x1bW4gbmFtZXMgdG8gb3JkZXIgYnkuIA0KDQpgYGB7cn0NCmFycmFuZ2UoZmxpZ2h0cywgeWVhciwgbW9udGgsIGRheSkNCmBgYA0KDQoNClVzZSBgZGVzYygpYCB0byByZS1vcmRlciBieSBhIGNvbHVtbiBpbiBkZXNjZW5kaW5nIG9yZGVyOg0KDQpgYGB7cn0NCmFycmFuZ2UoZmxpZ2h0cywgZGVzYyhhcnJfZGVsYXkpKQ0KYGBgDQoNCiMgU2VsZWN0IGNvbHVtbnMgd2l0aCBgc2VsZWN0KClgDQoNCg0KYHNlbGVjdCgpYCBhbGxvd3MgeW91IHRvIHJhcGlkbHkgem9vbSBpbiBvbiBhIHVzZWZ1bCBzdWJzZXQgdXNpbmcgdGhlIG5hbWVzIG9mIHRoZSAqKnZhcmlhYmxlcyoqLg0KDQoNCg0KYGBge3IsIGVjaG89VFJVRSwgZXZhbD1GQUxTRX0NCiMgU2VsZWN0IGNvbHVtbnMgYnkgbmFtZQ0Kc2VsZWN0KGZsaWdodHMsIHllYXIsIG1vbnRoLCBkYXkpDQpgYGANCg0KDQpUaGVyZSBhcmUgYSBudW1iZXIgb2YgaGVscGVyIGZ1bmN0aW9ucyB5b3UgY2FuIHVzZSB3aXRoaW4gYHNlbGVjdCgpYDoNCg0KYHN0YXJ0c193aXRoKCJhYmMiKWA6IG1hdGNoZXMgbmFtZXMgdGhhdCBiZWdpbiB3aXRoIGAiYWJjImAuDQoNCmBlbmRzX3dpdGgoInh5eiIpYDogbWF0Y2hlcyBuYW1lcyB0aGF0IGVuZCB3aXRoIGAieHl6ImAuDQoNCmBjb250YWlucygiaWprIilgOiBtYXRjaGVzIG5hbWVzIHRoYXQgY29udGFpbiBgImlqayJgLg0KDQoNCg0KIyBBZGQgbmV3IHZhcmlhYmxlcyB3aXRoIGBtdXRhdGUoKWANCg0KDQpUbyBfYWRkIG5ldyBjb2x1bW5zXyB0aGF0IGFyZSBmdW5jdGlvbnMgb2YgZXhpc3RpbmcgY29sdW1ucy4gVGhhdCBpcyB0aGUgam9iIG9mIGBtdXRhdGUoKWAuIGBtdXRhdGUoKWAgYWx3YXlzIGFkZHMgbmV3IGNvbHVtbnMgYXQgdGhlIGVuZCBvZiB5b3VyIGRhdGFzZXQuIA0KDQoNCmBgYHtyfQ0KIyBjcmVhdGUgYSBzbWFsbGVyIGRhdGFzZXQgd2l0aCBsZXNzIGNvbHVtbnMNCmZsaWdodHNfc21sIDwtIHNlbGVjdChmbGlnaHRzLA0KICB5ZWFyOmRheSwgDQogIGVuZHNfd2l0aCgiZGVsYXkiKSwgDQogIGRpc3RhbmNlLCANCiAgYWlyX3RpbWUNCikNCmZsaWdodHNfc21sDQpgYGANCg0KDQoNCiMgRXhhbXBsZTogdXNpbmcgYG11dGF0ZSgpYA0KDQoNCg0KYGBge3J9DQptdXRhdGUoZmxpZ2h0c19zbWwsDQogICAgICAgZ2FpbiA9IGFycl9kZWxheSAtIGRlcF9kZWxheSwNCiAgICAgICBzcGVlZCA9IGRpc3RhbmNlIC8gYWlyX3RpbWUgKiA2MA0KKQ0KZmxpZ2h0c19zbWwNCmBgYA0KDQpOb3RlIHRoYXQgeW91IGNhbiByZWZlciB0byBjb2x1bW5zIHRoYXQgeW91IGhhdmUganVzdCBjcmVhdGVkOg0KDQoNCmBgYHtyfQ0KbXV0YXRlKGZsaWdodHNfc21sLA0KICAgICAgIGdhaW4gPSBhcnJfZGVsYXkgLSBkZXBfZGVsYXksDQogICAgICAgaG91cnMgPSBhaXJfdGltZS82MCwNCiAgICAgICBnYWluX3Blcl9ob3VyID0gZ2Fpbi9ob3Vycw0KKQ0KYGBgDQoNCg0KDQojIEdyb3VwZWQgc3VtbWFyaWVzIHdpdGggYHN1bW1hcmlzZSgpYA0KDQpgc3VtbWFyaXNlKClgIF9jb2xsYXBzZXNfIGEgZGF0YSBmcmFtZSB0byBhIHNpbmdsZSByb3c6DQoNCmBgYHtyfQ0Kc3VtbWFyaXNlKGZsaWdodHMsIGRlbGF5ID0gbWVhbihkZXBfZGVsYXksIG5hLnJtID0gVFJVRSkpDQpgYGANCg0KDQpUaGUgYHN1bW1hcmlzZSgpYCBmdW5jdGlvbiBpcyB1c2VmdWwgd2hlbiB3ZSBwYWlyIGl0IHdpdGggYGdyb3VwX2J5KClgLg0KDQpUaGlzIHdheSwgdGhlIGFuYWx5c2lzIGNhbiBiZSBkb25lIGZvciBpbmRpdmlkdWFsIGdyb3Vwcy4gDQoNCg0KDQojIFRoZSBwaXBlIG9wZXJhdG9yDQoNCg0KVGhlIHBpcGUgb3BlcmF0b3IgaXMgZ2l2ZW4gYnkgKipgJT4lYCoqIChmcm9tIHRoZSBgbWFncml0dHJgIFtwYWNrYWdlXShodHRwczovL21hZ3JpdHRyLnRpZHl2ZXJzZS5vcmcvcmVmZXJlbmNlL3BpcGUuaHRtbCkpDQoNCg0KVGhlIHBpcGUgc2VuZHMgdGhlIG91dHB1dCBvZiB0aGUgTEhTIGZ1bmN0aW9uIHRvIHRoZSBmaXJzdCBhcmd1bWVudCBvZiB0aGUgUkhTIGZ1bmN0aW9uLg0KRm9yIGV4YW1wbGU6IA0KYGBge3J9DQojIHBpcGUgZXhhbXBsZQ0Kc3VtKDE6OCkgJT4lDQogIHNxcnQoKSAlPiUgDQogIGxvZygpDQpgYGANCg0KDQoNCmlzIGVxdWl2YWxlbnQgdG8gDQoNCmBgYHtyfQ0KIyB0aGUgbG9nIG9mIHRoZSBzcXVhcmUgcm9vdA0KIyBvZiB0aGUgc3VtIG9mIHRoZSBlbGVtZW50cyANCiMgb2YgdGhlIHZlY3RvciBbMSAuLi4gOF0NCmxvZyhzcXJ0KHN1bSgxOjgpKSkNCmBgYA0KDQoNCiMgRXhhbXBsZSBmb3IgZGF0YSBleHBsb3JhdGlvbg0KDQpJbWFnaW5lIHRoYXQgd2Ugd2FudCB0byBleHBsb3JlIHRoZSByZWxhdGlvbnNoaXAgYmV0d2VlbiB0aGUgZGlzdGFuY2UgYW5kIGF2ZXJhZ2UgZGVsYXkgZm9yIGVhY2ggbG9jYXRpb24uIA0KDQoNCg0KVGhlcmUgYXJlIHRocmVlIHN0ZXBzIHRvIHByZXBhcmUgdGhpcyBkYXRhOg0KDQoxLiAqKkdyb3VwKiogZmxpZ2h0cyBieSBkZXN0aW5hdGlvbi4NCg0KMi4gKipTdW1tYXJpemUqKiB0byBjb21wdXRlIGRpc3RhbmNlLCBhdmVyYWdlIGRlbGF5LCBhbmQgbnVtYmVyIG9mIGZsaWdodHMuDQoNCjMuICoqRmlsdGVyKiogdG8gcmVtb3ZlIG5vaXN5IHBvaW50cyBhbmQgSG9ub2x1bHUgYWlycG9ydCwgd2hpY2ggaXMgYWxtb3N0IHR3aWNlIGFzIGZhciBhd2F5IGFzIHRoZSBuZXh0IGNsb3Nlc3QgYWlycG9ydC4NCg0KDQoNCg0KIyBQb3dlciBvZiB0aGUgcGlwZSBgJT4lYCBvcGVyYXRvciANCg0KDQpgYGB7cn0NCiMgY3JlYXRlIGRhdGFmcmFtZSBvZiAiZGVsYXlzIg0KZGVsYXlzIDwtIGZsaWdodHMgJT4lDQogIGdyb3VwX2J5KGRlc3QpICU+JQ0KICBzdW1tYXJpc2UoDQogICAgY291bnQgPSBuKCksDQogICAgZGlzdCA9IG1lYW4oZGlzdGFuY2UsIG5hLnJtID0gVFJVRSksDQogICAgZGVsYXkgPSBtZWFuKGFycl9kZWxheSwgbmEucm0gPSBUUlVFKQ0KICApICU+JQ0KICBmaWx0ZXIoY291bnQgPiAyMCwgZGVzdCAhPSAiSE5MIikNCmRlbGF5cw0KYGBgDQoNCklmIHlvdSB1c2UgUlN0dWRpbywgeW91IGNhbiB0eXBlIHRoZSBwaXBlIHdpdGggQ3RybCArIFNoaWZ0ICsgTSBpZiB5b3UgaGF2ZSBhIFBDIG9yIENtZCArIFNoaWZ0ICsgTSBpZiB5b3UgaGF2ZSBhIE1hYy4NCg0KWW91IGNhbiByZWFkIGl0IGFzIGEgc2VyaWVzIG9mIGltcGVyYXRpdmUgc3RhdGVtZW50czogKipncm91cCoqLCB0aGVuICoqc3VtbWFyaXplKiosIHRoZW4gKipmaWx0ZXIqKi4gQSBnb29kIHdheSB0byBwcm9ub3VuY2UgYCU+JWAgd2hlbiByZWFkaW5nIGNvZGUgaXMgInRoZW4iLg0KDQpUaGUgYG4oKWAgZnVuY3Rpb24gaXMgaW1wbGVtZW50ZWQgc3BlY2lmaWNhbGx5IGZvciBlYWNoIGRhdGEgc291cmNlIGFuZCBjYW4gYmUgdXNlZCBmcm9tIHdpdGhpbiBgc3VtbWFyaXplKClgLCBgbXV0YXRlKClgIGFuZCBgZmlsdGVyKClgLiBJdCByZXR1cm5zIHRoZSBudW1iZXIgb2Ygb2JzZXJ2YXRpb25zIGluIHRoZSBjdXJyZW50IGdyb3VwLg0KDQoNCg0KIyBUcmFuc2Zvcm1hdGlvbnMNCg0KDQoqKldoeSBgbmEucm0gPSBUUlVFYCA/KiogQWdncmVnYXRpb24gZnVuY3Rpb25zIG9iZXkgdGhlIHVzdWFsIHJ1bGUgb2YgbWlzc2luZyB2YWx1ZXM6IGlmIHRoZXJlIGlzIGFueSBtaXNzaW5nIHZhbHVlIGluIHRoZSBpbnB1dCwgdGhlIG91dHB1dCB3aWxsIGJlIGEgbWlzc2luZyB2YWx1ZSEgDQoNCg0KYGBge3J9DQpmbGlnaHRzICU+JQ0KICBncm91cF9ieSh5ZWFyLCBtb250aCwgZGF5KSAlPiUNCiAgc3VtbWFyaXplKG1lYW4gPSBtZWFuKGRlcF9kZWxheSwgbmEucm0gPSBUUlVFKSkNCmBgYA0KDQoNCg0KDQo=